package ch.bfh.abcm.rems.controller;

import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Observable;

import ch.bfh.abcm.rems.controller.MessageElement.MulticastType;
import ch.bfh.abcm.rems.controller.model.Treshhold;
import ch.bfh.abcm.rems.controller.model.TreshholdManager;
import ch.bfh.abcm.rems.gateway.GateWayMessageType;
import ch.bfh.abcm.rems.gateway.GatewayMessage;
import ch.bfh.abcm.rems.interfaces.REMSMessageReceiver;
import ch.bfh.abcm.rems.konfiguration.EventMessage;
import ch.bfh.abcm.rems.konfiguration.UpdateNodeConfigMessage;
import ch.bfh.abcm.rems.konfiguration.UpdateUserConfigMessage;
import ch.bfh.abcm.rems.sensors.tinkerforge.TFSensorType;
import ch.bfh.abcm.rems.tinkerforge.TFNodeAverageMessage;

public class MulticastController extends Observable implements REMSMessageReceiver{
    
    private int okCounter;;
    private int MIN_AMOUNT_OF_OK = 4;
    private HashMap<TFSensorType, GateWayMessageType> stateMap;
    private TreshholdManager  treshholdManager;


    public MulticastController() {
        treshholdManager = new TreshholdManager();
        stateMap = new HashMap<TFSensorType, GateWayMessageType>();
        okCounter = 0;
    }
    
    @Override
    public void receivedGateWayMessage(GatewayMessage gatewayMessage) {
        boolean systemOKBefore = false;
        if (gatewayMessage.getSensorType() != null) {
            // Sensortype of PeriodicNotification is NULL
            systemOKBefore = stateMapAllOK();
            System.out.println("systemOKBefore "+systemOKBefore);
            stateMap.put(gatewayMessage.getSensorType(), gatewayMessage.getMessageType());
        }

        switch (gatewayMessage.getMessageType()) {
        case PERIODIC_NOTIFICATION:
            if (externNotificationIsNecessary(gatewayMessage.getMessageType())) {
                observeraction(MulticastType.PERIODIC_NOTIFICATION, gatewayMessage.getMessage());
            }
            break;
        case MEASUREMENT_DIVERGENCE:
            // its the same behavior
        case SYSTEM_ERROR:
            if (externNotificationIsNecessary(gatewayMessage.getMessageType())) {
                observeraction(MulticastType.SYSTEM_ERROR, gatewayMessage.getMessage());
            }
            break;
        case SYSTEM_OK:
            boolean systemOKAfter = stateMapAllOK();
            System.out.println("StateMap: "+stateMap);
            System.out.println("SystemOK: "+okCounter);
            // it is more readable than !systemOKBefore
            if ((systemOKBefore == false) && (systemOKAfter == true)) {
                okCounter = 0;
            }
            // it is more readable... 
            if ((systemOKBefore == true)&& (systemOKAfter == true)) {
                if (okCounter <=10) { // prevents from overlap
                    okCounter++;
                }
            }
            if (systemOKAfter = false) {
                okCounter = 0;
            }
            
            if (okCounter == MIN_AMOUNT_OF_OK) {
                // if and only if =)
                observeraction(MulticastType.SYSTEM_OK, gatewayMessage.getMessage());
            }
            break;
        }

    }
    
    private void observeraction(MessageElement.MulticastType multicastType, String message){
        MessageElement element = new MessageElement(multicastType, message);
        setChanged();
        notifyObservers(element);
        clearChanged();
    }

    private boolean stateMapAllOK() {
        boolean isok = true;
        Iterator<Entry<TFSensorType, GateWayMessageType>> iterator = stateMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<TFSensorType, GateWayMessageType> entry = iterator.next();
            if (entry.getKey() == null) {
                // Sensortype of PeriodicNotification is NULL
                continue;
            }
            isok = isok && entry.getValue().equals(GateWayMessageType.SYSTEM_OK);
        }
        return isok;
    }
    
    private boolean externNotificationIsNecessary(GateWayMessageType gateWayMessageType) {
        Treshhold treshhold = treshholdManager.getTreshHold(gateWayMessageType);
        if (treshhold == null) {
            Treshhold newTreshhold = createNewTreshhold(gateWayMessageType);
            treshholdManager.addTH(newTreshhold, gateWayMessageType);
            return true;
        }
        long timeOutInSeconds = treshhold.getTimeout() * 1000;
        long nextMessageTime = treshhold.getTimestampLastMessage() + timeOutInSeconds;
        long now = new Date().getTime();
        boolean notificationIsNecessary = (now >= nextMessageTime);
        if (notificationIsNecessary) {
            treshhold.setTimeout(getTimeoutFromPreferences(gateWayMessageType));
            treshhold.setTimestampLastMessage(now);
            treshholdManager.addTH(treshhold, gateWayMessageType);
            return true;
        }
        return false;
    }
    
    private Treshhold createNewTreshhold(GateWayMessageType gateWayMessageType) {
        long now = new Date().getTime();
        long timeout = getTimeoutFromPreferences(gateWayMessageType);
        Treshhold newTreshhold = new Treshhold(gateWayMessageType, timeout, now);
        return newTreshhold;
    }
    
    private long getTimeoutFromPreferences(GateWayMessageType gateWayMessageType) {
            switch (gateWayMessageType) {
            case PERIODIC_NOTIFICATION:
                return 10;
            case MEASUREMENT_DIVERGENCE:
                return 8;
            case SYSTEM_ERROR:
                return 8;
            case SYSTEM_OK:
                return 0;
            default:
                break;
            }
        return 0;
    }

    
    @Override
    public void receivedTFNodeAverageMessage(TFNodeAverageMessage nodeAverageMessage) {
        // Nothing to do here
    }

    @Override
    public void receivedUpdateUserConfigMessage(UpdateUserConfigMessage updateUserConfigMessage) {
        // Nothing to do here
    }

    @Override
    public void receivedUpdateNodeConfigMessage(UpdateNodeConfigMessage updateNodeConfigMessage) {
        // Nothing to do here
    }

    @Override
    public void receivedEventMessage(EventMessage eventMessage) {
        // Nothing to do here
    }

}
